package ordonnanceur;

import generationTache.IGenerationTache;

import java.io.FileNotFoundException;
import java.util.ArrayList;

import outils.ManipulationFichier;
import outils.MathTR;

/**
 * Permet d'appliquer aux taches le couple d'ordonnanceur EDF/BG
 * @author nicolas rault et matthieu allon
 *
 */
public class EDF_BG 
{
	private ArrayList<IGenerationTache> listeTacheAperiod;
	private ArrayList<IGenerationTache> listeTachePeriod;
	private ManipulationFichier<IGenerationTache> outils;
	//Defintion de la liste de string a renvoyer a kiwi
	private ArrayList<String> listKiwi;
	private int ppcm;
	private int duration;
	
	/**
	 * Constructeur : permet de charger les taches periodiques et aperiodiques a partir de fichiers deja existant.
	 */
	public EDF_BG()
	{
		duration = 0;
		//Lecture des fichiers contenant les taches periodiques et apériodiques
		lectureFichier();
	}
	
	/**
	 * Permet la generation de la liste necessaire à Kiwi
	 */
	public void ordonnancement()
	{
		//EDF_BG : donc, utilisation de EDF pour les taches periodiques	
		//Classement EDF selon la di absolue
		ordonnancementEDF();	
		
		//Calcul du PPCM pour les taches périodiques
		calculPPCM();
		
		//generation de la liste des taches pour kiwi
		generationListKiwi();
	}
	
	/**
	 * Effectue le test d'ordonnancement de l'algorithme
	 * @return "Le système est ordonnançable" si le test est verifie, "On ne sait pas si le système est ordonnançable" sinon
	 */
	public String testOrdonnancement()
	{
		String condition;
		double U = 0;
		
		for(int i = 0; i<listeTachePeriod.size(); i++)
		{
			IGenerationTache tacheTmp = listeTachePeriod.get(i);
			U += (tacheTmp.getCi()/tacheTmp.getPi());
		}
		if(U<=1)
		{
			condition = "Le système est ordonnançable";
		}
		else
		{
			condition = "On ne sait pas si le système est ordonnançable";
		}		
		
		return condition;
	}
	
	/**
	 * Permet de trier les taches periodiques selon leur Di
	 */
	private void ordonnancementEDF()
	{
		//Le classement selon EDF doit être réalisé selon la date d'échéance absolue la plus petite
		//(Autrement dit : selon la date di la plus petite à chaque instant ==> réveil et fin d'exécution
		//Donc : ci - di
		
		for( int i = 0; i < listeTachePeriod.size(); i ++)
		{
			int DiI = listeTachePeriod.get(i).getCi() - listeTachePeriod.get(i).getDi();
			for( int j = 0; j < listeTachePeriod.size(); j ++)
			{
				int DiJ = listeTachePeriod.get(j).getCi() - listeTachePeriod.get(j).getDi();
				
				if (DiJ < DiI)
				{
					IGenerationTache listeTacheTmp = listeTachePeriod.get(i);
					listeTachePeriod.get(i).equals(listeTachePeriod.get(j));
					listeTachePeriod.get(j).equals(listeTacheTmp);
				}
			}
		}
	}
	
	/**
	 * Permet de recuperer les taches periodiques et aperiodiques des fichiers existants (TAcheP.txt et TacheAp.txt)
	 */
	private void lectureFichier()
	{
		listeTacheAperiod = new ArrayList<IGenerationTache>();		
		listeTachePeriod = new ArrayList<IGenerationTache>();
		outils = new ManipulationFichier<IGenerationTache>();
		try {
			listeTacheAperiod = outils.read("TacheAp.txt");
			listeTachePeriod = outils.read("TacheP.txt");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Permet de calculer le ppcm par rapport aux Di des taches periodiques
	 */
	private void calculPPCM()
	{
		//Calcul du PPCM		
		ppcm =0;
		if(listeTachePeriod.size() > 2)
		{
			ppcm = MathTR.calculePPCM(listeTachePeriod.get(0).getPi(), listeTachePeriod.get(1).getPi());
			for(int i = 2; i<listeTachePeriod.size(); i++)
			{
				ppcm = MathTR.calculePPCM(ppcm, listeTachePeriod.get(i).getPi());
			}
		}
		else
		{
			if(listeTachePeriod.size() == 2)
			{
				ppcm = MathTR.calculePPCM(listeTachePeriod.get(0).getPi(),listeTachePeriod.get(1).getPi());
			}
			else
			{
				ppcm = listeTachePeriod.get(0).getPi();
			}
		}
	}
	
	/**
	 * Permet de trier les taches aperiodiques suivant leur ri, et donc suivant leur ordre d'apparition
	 */
	private void triTacheAp()
	{
		if(listeTacheAperiod.size() >0)
		{
			boolean fini = false;
			while(!fini){
				fini = true;
				for(int i =0;i<listeTacheAperiod.size();i++)
				{
					if((i+1)<listeTacheAperiod.size()){
						if(listeTacheAperiod.get(i).getRi() > listeTacheAperiod.get(i+1).getRi()){
							IGenerationTache Tmp = listeTacheAperiod.get(i);
							listeTacheAperiod.remove(i);
							listeTacheAperiod.add(i+1,Tmp);
							fini = false;
						}
					}
				}
			}
		}
	}
		
	/**
	 * Permet de generer la liste necessaire au parseur du fichier kiwi
	 */
	private void generationListKiwi()
	{			
		System.out.println("Début du calcul de l'ordonnancement EDF/BG.");
		listKiwi = new ArrayList<String>();
		triTacheAp();
		//Toutes les Taches Periodiques sont pretes, et leur durée au max
		for(int i = 0; i<listeTachePeriod.size(); i++)
		{
			listeTachePeriod.get(i).setActive(true);
			listeTachePeriod.get(i).setDuree(listeTachePeriod.get(i).getCi());
		}
		//Toutes les Taches Apériodiques ont leur durée de connu
		for(int i = 0; i<listeTacheAperiod.size(); i++)
		{
			listeTacheAperiod.get(i).setDuree(listeTacheAperiod.get(i).getCi());
		}
		int i = 0;
		int tachePrecedente = -1;
		boolean tachePrecedenteActive = false;
		boolean isPeriodique = true;
		int nbPreemption = 0;
		int nbChangementContexte = 0;
		int nbViolationEcheance = 0;
		int compteur = 100;
		boolean allApDone = false;
		//Ne marche toujours pas
		while(i<ppcm && compteur>0)
		{
			int a =0;
			if(i != 0)
			{
				//On vérifie si des taches Periodiques doivent passer en active et on remet a jour leur duree
				for(a = 0; a<listeTachePeriod.size() && i != 0; a++)
				{
					if(i%listeTachePeriod.get(a).getPi() == 0)
					{
						listKiwi.add("STOP "+a);
						if(listeTachePeriod.get(a).getDuree() != 0)
						{
							nbViolationEcheance++;
						}
						listeTachePeriod.get(a).setDuree(listeTachePeriod.get(a).getCi());
						listeTachePeriod.get(a).setActive(true);
						listeTachePeriod.get(a).setRi(listeTachePeriod.get(a).getRi()+listeTachePeriod.get(a).getPi());
						listeTachePeriod.get(a).setDi(listeTachePeriod.get(a).getDi()+listeTachePeriod.get(a).getPi());
					}
				}
			}
			//On fait la meme chose pour les taches Apériodiques
			for(a = 0; a<listeTacheAperiod.size(); a++)
			{
				if(i == listeTacheAperiod.get(a).getRi())
				{
					listeTacheAperiod.get(a).setActive(true);
				}
			}
			
			int tache = -1;
			int petitDi = Integer.MAX_VALUE;
			int petitRi = Integer.MAX_VALUE;
			//On vérifie qu'une tache périodique soit prete
			for(a = 0; a<listeTachePeriod.size(); a++)
			{
				if(listeTachePeriod.get(a).getActive())
				{
					if(listeTachePeriod.get(a).getDi() < petitDi)
					{
						tache = a;
						petitDi = listeTachePeriod.get(a).getDi();
						petitRi = listeTachePeriod.get(a).getRi();
					}
					else if(listeTachePeriod.get(a).getDi() == petitDi)
					{
						if(listeTachePeriod.get(a).getRi() < petitRi)
						{
							tache = a;
							petitDi = listeTachePeriod.get(a).getDi();
							petitRi = listeTachePeriod.get(a).getRi();
						}
					}
				}	
			}	
			//On a trouve une tache périodique
			if(tache != -1)
			{
				listeTachePeriod.get(tache).setDuree(listeTachePeriod.get(tache).getDuree()-1);
				if(listeTachePeriod.get(tache).getDuree() == 0)
				{
					listeTachePeriod.get(tache).setActive(false);
				}
				//On vérifie si la tache précédemment traitée était une apériodique
				if(!isPeriodique)
				{
					nbChangementContexte++;
					isPeriodique = true;
				}
			}
			//Si aucune tache périodique n'est prete, on regarde les taches Apériodiques
			else
			{
				boolean find = false;
				for(a = 0;a<listeTacheAperiod.size() && !find; a++)
				{
					if(listeTacheAperiod.get(a).getActive())
					{
						if(listeTacheAperiod.get(a).getDuree() == listeTacheAperiod.get(a).getCi())
						{
							//Premiere fois que la tache est utilisée
							//On sauvegarde le i dans le Pi qui est inutilisé pour les Apériodiques
							listeTacheAperiod.get(a).setPi(i);
						}
						listeTacheAperiod.get(a).setDuree(listeTacheAperiod.get(a).getDuree()-1);
						if(listeTacheAperiod.get(a).getDuree() == 0)
						{
							listeTacheAperiod.get(a).setActive(false);
							//Derniere fois que la tache est utilisée
							//On sauvegarde le i dans le Di qui est inutilisée pour les Apériodiques
							listeTacheAperiod.get(a).setDi(i);
						}
						tache = listeTachePeriod.size()+a;
						find = true;
						//On vérifie si la tache précédente était une tache périodique
						if(isPeriodique)
						{
							nbChangementContexte++;
							isPeriodique = false;
						}
					}
				}
			}
			listKiwi.add(""+tache);
			if(i!=0)
			{
				if(tachePrecedente != tache)
				{	
					//On vérifie les préemptions
					if(tachePrecedenteActive)
					{
						nbPreemption++;
					}
				}
			}
			//On met a jour la tache précédente avec la tache actuelle.
			tachePrecedente = tache;
			if(tache != -1)
			{
				if(tache>listeTachePeriod.size()-1){
					tachePrecedenteActive = listeTacheAperiod.get(tache-listeTachePeriod.size()).getActive();
				}
				else
				{
					tachePrecedenteActive = listeTachePeriod.get(tache).getActive();
				}
			}
			else
			{
				tachePrecedenteActive = false;
			}
			//On vérifie si toutes les taches Apériodiques ont été effectués
			if(listeTacheAperiod.size()>0 && !allApDone)
			{
				boolean allDone = true;
				for(int b = 0; b<listeTacheAperiod.size(); b++)
				{
					if(listeTacheAperiod.get(b).getDuree() != 0)
					{
						allDone = false;
					}
				}
				if(allDone)
				{
					System.out.println("Toutes les taches Ap ont été exécutées.");
					allApDone = true;
				}
			}
			if(allApDone)
			{
				compteur--;
			}
			
			i++;
		}
		int borneMin = Integer.MAX_VALUE;
		int borneMax = 0;
		int moyenne = 0;
		//On regarde si toutes les taches apériodiques ont été effectués
		for(int a = 0;a<listeTacheAperiod.size(); a++)
		{
			if(listeTacheAperiod.get(a).getDuree() != 0)
			{
				nbViolationEcheance++;
			}
			int temps = listeTacheAperiod.get(a).getDi() - listeTacheAperiod.get(a).getPi();
			if(temps < borneMin)
			{
				borneMin = temps;
			}
			if(temps > borneMax)
			{
				borneMax = temps;
			}
			moyenne += temps;
		}
		if(listeTacheAperiod.size()>0)
		{
			moyenne = moyenne/listeTacheAperiod.size();
		}
		duration = i;
		ManipulationFichier<IGenerationTache> outils = new ManipulationFichier<IGenerationTache>();
		outils.sauvegardePerformance("rmbg", borneMin, borneMax, moyenne, nbPreemption, nbChangementContexte, nbViolationEcheance);
		System.out.println("Fin du calcul de l'ordonnancement.");
	}
			
	/**
	 * Permet de recuperer la liste necessaire au parseur du fichier kiwi
	 * @return la liste necessaire au parseur du fichier kiwi
	 */
	public ArrayList<String> getListeKiwi() {
		return listKiwi;
	}
	
	/**
	 * Permet de recuperer la duree (en pas) de l'ordonnancement
	 * @return la duration necessaire au fichier kiwi
	 */
	public int getDuration()
	{
		return duration;
	}

}
